//
// Created by 67678 on 2023/3/12.
//

#include "socketClient.h"
#include <cassert>
#include <thread>
#include <mutex>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include "log.h"
#include "socketUtil.h"

using namespace std;
using namespace socket_epoll;

SocketClient::SocketClient() {
    log("socketClient start running!");

}

SocketClient::~SocketClient() {
    log("socketClient end running!");

}

int SocketClient::connect(int ip, int port) {
    socket_ = socket(AF_INET, SOCK_STREAM, 0);
    if (socket_ == -1) {
        log(fmt::format("socket failed with code {}, error msg: {}", errno, ::strerror(errno)));
        throw runtime_error("socket failed!");
    }

    sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(port);
    server_addr.sin_addr.s_addr = ip;
    int fd = ::connect(socket_, reinterpret_cast<sockaddr *>(&server_addr), sizeof(server_addr));
    if (fd == -1) {
        string msg = fmt::format("socket failed with code {}, error msg: {}", errno, ::strerror(errno));
        cout << msg << endl;
        log(msg);
        throw runtime_error("socket failed!");
    }
    return socket_;
}


void SocketClient::init(int fd) {
    socket_ = fd;
}

int SocketClient::getFD() const {
    return socket_;
}

int SocketClient::nonBlockingWrite(std::string_view sv) {
    while (!sv.empty()) {
        int tmp = ::write(socket_, sv.data(), sv.size());
        if (tmp < 0) {
            if (errno == EINTR) throw runtime_error("write failed with error EINTR");
            if (errno == EAGAIN) this_thread::yield();
            else {
                ::perror("nonBlockingWrite failed with error unknown");
                throw runtime_error("write failed with error unknown");
            }
        }
        else {
            sv = sv.substr(tmp);
        }
    }
    return sv.size();
}

int SocketClient::blockingWrite(std::string_view sv) {
    int ret = ::write(socket_, sv.data(), sv.size());

    return ret;
}

std::string SocketClient::nonBlockingRead() {
    constexpr int BUFFER_SIZE = 1024;
    char buffer[BUFFER_SIZE];
    string ret;
    while (true) {
        ::memset(buffer, '\0', BUFFER_SIZE);
        int read_size = recv(socket_, buffer, BUFFER_SIZE, 0);
        if (read_size == 0) {
            return ret;
        }
        if (read_size < 0) {

            log(fmt::format("recv failed with code {}, error msg: {}", errno, ::strerror(errno)));
            if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR || errno == EINVAL)
                break;
            ::perror("recv failed");
            throw runtime_error("recv failed!");
        }
        ret.append(buffer, read_size);
    }
    return ret;
}

std::string SocketClient::blockingRead() {
    constexpr int BUFFER_SIZE = 1024;
    char buffer[BUFFER_SIZE];
    string ret;
    int read_size = recv(socket_, buffer, BUFFER_SIZE, 0);
    if (read_size == 0) {
        string msg = fmt::format("socket failed with code {}, error msg: {}", errno, ::strerror(errno));
        cout << msg << endl;
        log(msg);
        return ret;
    }
    if (read_size < 0) {
        string msg = fmt::format("socket failed with code {}, error msg: {}", errno, ::strerror(errno));
        cout << msg << endl;
        log(msg);
        return ret;
    }
    ret.append(buffer, read_size);

    return ret;
}

int SocketClient::write(string_view sv) {

    if (blocking_) return blockingWrite(sv);
    else return nonBlockingWrite(sv);
}

std::string SocketClient::read() {
    if (blocking_) return blockingRead();
    else return nonBlockingRead();
}

void SocketClient::close() {
    if (socket_ != -1) {
        ::close(socket_);
        socket_ = -1;
    }

}

void SocketClient::setNonBlocking() {
    blocking_ = false;
    ::setNonblocking(socket_);
}


